package language;

import org.apache.commons.codec.binary.Hex;
import org.junit.Test;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Scanner;
import java.util.stream.Stream;

import static java.nio.file.StandardWatchEventKinds.*;

public class NioFileOperation {

    @Test
    public void createPath() throws IOException {
        //Path是一个接口,它用来表示文件系统的路径,可以指向文件或文件夹。
        //Paths是一个用来生成Path的工具类，功能很单一
        Path p = Paths.get("NioFileOperation.java").toAbsolutePath();  //可以是目录，也可以是文件
        Path p1 = Paths.get("D:\\a\\b\\test.txt");
        System.out.println(p.getClass());
        //path的增删操作
        p1.normalize(); // 去除 . 和 .. 这样的路径
        Path resolve = p1.getParent().resolve("a.txt"); // 返回"D:\\a\\b\\a.txt"
        Path relativize = p1.getParent().relativize(p1); //构造此路径和给定路径之间的相对路径
        Path path = p1.toRealPath();


        //转成file对象
        File a = new File(p.toString()); //new File(filepath)  默认定位到"user.dir"下，就是当前的工作目录
        File b = p.toFile();
        System.out.println(p.getFileName()); //返回的是path接口，toString才能获得文件名

        //依次获取path里的元素
        for(int i = 0; i < p.getNameCount(); i++)  System.out.println(p.getName(i));

        //endwith 最后一个appendFile.java
        System.out.println("ends with 'NioFileOperation.java': " + p.endsWith("NioFileOperation.java"));

        //startsWith 是根目录 C:\
        System.out.println("Starts with " + p.getRoot() + " " + p.startsWith(p.getRoot()));
        // multifile.getSize()   file.length;

        Files.exists(p);
        Files.isRegularFile(p); //区分是否是普通文件（而不是目录

    }

    @Test
    public void writeFile1() throws IOException {
        Path tempPath = Files.createTempFile("prefix", "失败明细文件.tmp");
        byte[] utf8bom = {(byte) 0xef, (byte) 0xbb, (byte) 0xbf};
        Files.write(tempPath, utf8bom);
        Files.write(tempPath, "Enum".getBytes());
        Files.write(tempPath, "bbb".getBytes());
        System.out.println(tempPath.getFileName().toString());
    }


    //Files类提供大量方法，快速实现文件/目录的创建、复制、移动、删除以及文件读写操作。
    @Test
    public void writeFile() throws IOException {
        //创建临时文件
        Path tempPath = Files.createTempFile("prefix", "失败明细文件.tmp");

        //创建指定文件
        tempPath = Paths.get("D:\\a\\b\\test.txt");
        Files.createDirectories(tempPath.getParent()); //可以创建多级目录；如果已存在文件夹不会报错
//        Files.createFile(tempPath); //文件已存在会报错。没有文件夹会报错
        //写文件
        Files.write(tempPath, "".getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE); //可以代替Files.createFile，但没有文件夹的话会报错
        Files.write(tempPath, "one line\n".getBytes(StandardCharsets.UTF_8), StandardOpenOption.APPEND);
        Files.write(tempPath, "two line".getBytes(StandardCharsets.UTF_8), StandardOpenOption.APPEND);
        Files.delete(tempPath);
    }

    @Test
    public void readFile() throws IOException {
        Path path = Paths.get("NioFileOperation.java").toAbsolutePath();  //可以是目录，也可以是文件

        //逐行读文件
        Scanner scanner = new Scanner(path);
        while (scanner.hasNextLine()) {
            System.out.println(scanner.nextLine());
        }
        scanner.close();

        //得到一个stream，需要关闭流。惰性加载
        Stream<String> lines = Files.lines(path);
        lines.skip(0)
                .findFirst()
                .ifPresent(System.out::println);
        lines.close();

        try (Stream<String> lines2 = Files.lines(path, Charset.defaultCharset())) {
            lines2.forEachOrdered(System.out::println);
        }

        //一次性读入文件到内存。文件大可能OOM
        Files.readAllLines(path)
                .stream()
                .filter(line -> !line.startsWith("//"))
                .map(line -> line.substring(0, line.length()/2))
                .forEach(System.out::println);
    }

    static void show(String id, Object o) {
        System.out.println(id + ": " + o);
    }

    //获取文件系统信息
    @Test
    public void fileSystemInfo(){
        System.out.println(System.getProperty("os.name"));
        FileSystem fsys = FileSystems.getDefault();
        for(FileStore fs : fsys.getFileStores()) show("File Store", fs);
        for(Path rd : fsys.getRootDirectories()) show("Root Directory", rd);
        show("Separator", fsys.getSeparator());
        show("UserPrincipalLookupService", fsys.getUserPrincipalLookupService());
        show("isOpen", fsys.isOpen());
        show("isReadOnly", fsys.isReadOnly());
        show("FileSystemProvider", fsys.provider());
        show("File Attribute Views", fsys.supportedFileAttributeViews());
    }

    // 遍历文件夹
    @Test
    public void walk() throws IOException {
        Path p = Paths.get("src");
        System.out.println(p.toAbsolutePath());
        Files.walk(p).filter(f -> f.toString().endsWith(".java")).forEach(f -> {
                    System.out.println("walking " + f);
                });
    }

    // 文件变动监听
    @Test
    public void watch() throws IOException, InterruptedException {
        Path p = Paths.get("src");

        //监听器注册
        WatchService watcher = FileSystems.getDefault().newWatchService();
        p.register(watcher, ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE);

        while(true) {
            // 获取下一个文件改动事件。文件创建，文件删除，文件内容发生改动
            // 改文件名属于先删除再创建
            WatchKey key = watcher.take();
            for(WatchEvent evt : key.pollEvents()) {
                System.out.println("evt.context(): " + evt.context() + //文件名，修改前
                        "\nevt.count(): " + evt.count() +  //个数
                        "\nevt.kind(): " + evt.kind());  //修改种类
            }

            boolean valid = key.reset(); // 重设WatchKey
            if (!valid) break; // 如果重设失败，退出监听
        }
    }



    public static String getSHA256Str(String str) {
        MessageDigest messageDigest;
        String encdeStr = "";
        try {
            messageDigest = MessageDigest.getInstance("SHA-256");
            byte[] hash = messageDigest.digest(str.getBytes("UTF-8"));
            encdeStr = Hex.encodeHexString(hash);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return encdeStr;
    }
}
